
#include "QueryPreprocessor.h"
#include "DefinitionTable.h"
#include "QueryTree.h"
using namespace std;

	const string QueryPreprocessor::DIGITS = "1234567890";
	const string QueryPreprocessor::PUNCT = "_()<>;,.\"+-*";
	const string QueryPreprocessor::LETTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
	string QueryPreprocessor::nextToken;
	vector<string> QueryPreprocessor::tokenList;

	//parses and validates the query
	TNode QueryPreprocessor::parse(string query) {
		//create new definitionTable
		DefinitionTable defTable;
		defTable.clearTable();
		//tokenise the query
		tokenList = tokenise(query);
		//start parsing

		try {
			TNode tree;
			tree = QueryPreprocessor::query();
			//tree.toString();	//debuggy test
			return tree;
		}
		catch ( int e ) {
			return TNode(NONE, -1);
		}
	}

	//split query into tokens based on whitespaces and punctuations
	vector<string> QueryPreprocessor::tokenise(string query) {
		vector<string> tokenised;
		string token;
		for ( int i = 0; i < query.size(); ++i ) {
			if ( isPresent(LETTERS+DIGITS, query.at(i)) ) {
				token.push_back( query.at(i) );
			}
			else if ( isPresent(" \n\r\t", query.at(i) ) ) {
				if ( token.size()!=0 ) {
					tokenised.push_back(token);
					token = "";
				}
			}
			else if ( isPresent(PUNCT, query.at(i)) ) {
				if ( token.size()!=0 ) {
					tokenised.push_back(token);
					token = "";
				}
				token.push_back(query.at(i));
				tokenised.push_back(token);
				token = "";
			}
			else {
				cout << "Error! Invalid symbol!" <<endl;
				//ERROR
			}
		}
		if ( token != "" ) {
			tokenised.push_back(token);
		}
		return tokenised;
	}

	bool QueryPreprocessor::isPresent(string charSet, char targetChar) {
		if ( charSet.find(targetChar) == string::npos )
			return false;
		else
			return true;
	}

	bool QueryPreprocessor::isInteger(string checkString) {
		for ( int i = 0; i < checkString.length(); ++i ) {
			if ( !isPresent(DIGITS, checkString.at(i)) ) {
				return false;
			}
		}
		return true;
	}

	bool QueryPreprocessor::isIdent(string checkString) {
		if ( isPresent(LETTERS, checkString.at(0)) ) {
			for ( int i = 1; i < checkString.length(); ++i) {
				char tempChar = checkString.at(i);
				if ( !isPresent(LETTERS, tempChar) && !isPresent(DIGITS, tempChar) && !(tempChar=='#') ) {
					return false;
				}
			}
			return true;
		}
		else {
			return false;
		}
	}

	bool QueryPreprocessor::isName(string checkString) {
		if ( isPresent(LETTERS, checkString.at(0)) ) {
			for ( int i = 1; i < checkString.length(); ++i) {
				char tempChar = checkString.at(i);
				if ( !isPresent(LETTERS, tempChar) && !isPresent(DIGITS, tempChar) ) {
					return false;
				}
			}
			return true;
		}
		else {
			return false;
		}
	}

	bool QueryPreprocessor::isSynonym(string syn) {
		if ( isIdent(syn) && DefinitionTable::isNamePresent(syn) )
			return true;
		else
			return false;
	}

	string QueryPreprocessor::getToken() {
		if ( tokenList.size() != 0 ) {
			string token = tokenList.at(0);
			tokenList.erase( tokenList.begin() );
			return token;
		}
		else {
			return "";
		}
	}

	//query: declaration* select
	TNode QueryPreprocessor::query() {
		nextToken = getToken();

		while ( nextToken != "Select" ) {
			declaration();
		}

		return select();
	}

	//select: "Select" result ( suchThat | pattern | with )
	TNode QueryPreprocessor::select() {
		match("Select");
		TNode rootNode(SELECT, -1);
		QueryTree::setRoot(rootNode);

		//all these should be child to rootNode
		QueryTree::setParentChild(&rootNode, result());	//returning TNode
		
		while ( nextToken != "" ) {
			if ( nextToken == "such" ) {
				QueryTree::setParentChild(&rootNode, suchThat());
			}
			else if ( nextToken == "pattern" ) {
				pattern();
				//QueryTree::setParentChild(rootNode, pattern());
			}
			else if ( nextToken == "with" ) {
				with();
			}
			else {
				throw 0;
			}
		}
		return rootNode;
	}

	void QueryPreprocessor::match(string token) {
		if ( nextToken == token ) {
			nextToken = getToken();
		}
		else {
			throw 0;
		}
	}

	//parse results: tuple | BOOLEAN
	TNode QueryPreprocessor::result() {
		//create resultNode, return resultNode
		//if there are more results, they should be childs of resultNode
		TNode resultNode(RESULT,-1);

		if ( nextToken == "BOOLEAN" ) {
			match("BOOLEAN");
			//create "BOOLEAN" node
			TNode booleanNode(BOOLEAN, -1);
			QueryTree::setParentChild(&resultNode, booleanNode);
		}
		else {
			//create result node
			TNode synonymNode = elem();
			QueryTree::setParentChild(&resultNode, synonymNode);
			//resultNode = tuple();	
		}
	
		return resultNode;
	}

	//parse tuple: elem | "<" elem ( "," elem )* ">"
	//WRONG AND INCOMPLETE FUNCTION
	void QueryPreprocessor::tuple() {
		if ( nextToken == "elem" ) {
			elem();
		}
		else if ( nextToken == "<" ) {
			match("<");
			elem();
			while ( nextToken == "," ) {
				match(",");
				elem();
			}
			match(">");
		}
		else {
			throw 0;
		}
	}

	//elem: synonym | attrRef, attrRef: synonym "." attrName
	TNode QueryPreprocessor::elem() {
		//this could be synonym or attrRef. For 3201, its only synonym though.
		//the problem here is, how to differentiate?
		//IDEA: could use ERROR. if token after nextToken is not ".", it is definitely synonym
		return synonym();
	}

	//synonym: IDENT
	TNode QueryPreprocessor::synonym() {
		//check if synonym is valid, check if synonym is in DefTable, add in if it is not
		if ( isSynonym(nextToken) ) {
			//this will get the index of the synonym in DefTable
			//create the TNode for synonym and return
			TNode synonymNode(SYNONYM, DefinitionTable::getIndex(nextToken));
			nextToken = getToken();
			return synonymNode;
		}
		else {
			throw 0;
		}

		//if ( isIdent(nextToken) ) {
		//	if ( !DefinitionTable::isNamePresent(nextToken) ) {
		//		//THIS COULD BE ERROR INSTEAD
		//		DefinitionTable::setDefinition(NONE, nextToken);
		//		TNode synonymNode(SYNONYM, DefinitionTable::getIndex(nextToken));
		//		nextToken = getToken();
		//		return synonymNode;
		//	}
		//	else {
		//		//this will get the index of the synonym in DefTable
		//		//create the TNode for synonym and return
		//		TNode synonymNode(SYNONYM, DefinitionTable::getIndex(nextToken));
		//		nextToken = getToken();
		//		return synonymNode;
		//	}
		//}
		//else {
		//	throw 0;
		//}
	}

	//attrRef: synonym "." attrName
	void QueryPreprocessor::attrRef() {
		;//TO BE IMPLEMENTED
	}
	
	//entRef: synonym | "_" | """ IDENT """ | INTEGER
	void QueryPreprocessor::entRef() {
		;
	}

	//stmtRef: synonym | "_" | INTEGER
	TNode QueryPreprocessor::stmtRef() {
		if ( isIdent(nextToken) ) {
			//check if synonym is declared;
			if ( DefinitionTable::isNamePresent(nextToken) ) {
				return synonym();
			}
			else {
				throw 0;
			}
		}
		else if ( nextToken == "_" ) {
			match("_");
			TNode wildCardNode(WILDCARD, -1);
			return wildCardNode;
		}
		else if ( isInteger(nextToken) ) {
			TNode stmtNumNode(STMT, atoi(nextToken.c_str()) );
			nextToken = getToken();
			return stmtNumNode;
		}
		else {
			throw 0;
		}
	}

	//varRef: synonym | "_" | """ IDENT """
	TNode QueryPreprocessor::varRef() {
		if ( isIdent(nextToken) ) {
			//check if synonym is declared;
			if ( DefinitionTable::isNamePresent(nextToken) ) {
				return synonym();
			}
			else {
				throw 0;
			}
		}
		else if ( nextToken == "_" ) {
			match("_");
			TNode wildCardNode(WILDCARD, -1);
			return wildCardNode;
		}
		else if ( nextToken == "\"" ) {
			match("\"");
			if ( isIdent(nextToken) ) {
				//should i use VarTable to obtain the index of the IDENT? yes i shld!
				//find index of nextToken in VarTable
				int index = -1; //VarTable::getIndex(nextToken), -1 if not found
				TNode identNode(VAR, index);
				nextToken = getToken();
				return identNode;
			}
			else {
				throw 0;
			}
			match("\"");
		}
		else {
			throw 0;
		}
	}

	//lineRef: synonym | "_" | INTEGER
	void QueryPreprocessor::lineRef() {
		//TO BE IMPLEMENTED;
	}

	//declaration: designEntity synonym ( "," synonym )* ";"
	void QueryPreprocessor::declaration() {
		string name;
		TYPE type;
		type = designEntity();
		if ( isIdent(nextToken) ) {
			name = nextToken;
			nextToken = getToken();
		}
		DefinitionTable::setDefinition(type, name);
		while ( nextToken == "," ) {
			match(",");
			if ( isIdent(nextToken) ) {
				name = nextToken;
				nextToken = getToken();
			}
			DefinitionTable::setDefinition(type, name);
		}
		match(";");
	}

	//designEntity: "procedure" | "stmtLst" | "stmt" | "assign" | "call" | "while" | "if" | "variable" | "constant" | "prog_line" | plus | minus | times 
	TYPE QueryPreprocessor::designEntity() {
		if ( nextToken == "procedure" ) {
			match("procedure");
			return PROC;
		}
		else if ( nextToken == "stmtLst" ) {
			match("stmtLst");
			return STMTLST;
		}
		else if ( nextToken == "stmt" ) {
			match("stmt");
			return STMT;
		}
		else if ( nextToken == "assign" ) {
			match("assign");
			return ASSIGN;
		}
		//else if ( nextToken == "call" ) {
		//	;
		//}
		else if ( nextToken == "while" ) {
			match("while");
			return WHILE;
		}
		else if ( nextToken == "if" ) {
			;
		}
		else if ( nextToken == "variable" ) {
			match("variable");
			return VAR;
		}
		else if ( nextToken == "constant" ) {
			match("constant");
			return CONST;
		}
		//else if ( nextToken == "prog_line" ) {
		//	;
		//}
		else if ( nextToken == "plus" ) {
			match("plus");
			return PLUS;
		}
		//else if ( nextToken == "minus" ) {
		//	;
		//}
		//else if ( nextToken == "times" ) {
		//	;
		//}
		else {
			throw 0;
		}
		//
	}

	//such that: "such that" relRef ( "and" relRef )*
	TNode QueryPreprocessor::suchThat() {
		match("such");
		match("that");
		
		TNode baseSuchThatNode = relRef();
		
		//and...
		//while ( nextToken == "and" ) {
		//	relRef();
		//}

		return baseSuchThatNode;
	}

	//relRef = ModifiesP | ModifiesS | UsesP | UsesS | Calls | CallsT | Parent | ParentT | Follows | FollowsT | Next | NextT | Affects | AffectsT
	TNode QueryPreprocessor::relRef() {
		//should find out which relationship it is, and get the TNode that contains the relationship
		if ( nextToken == "Modifies" ) {
			match("Modifies");
			match("(");
			if ( nextToken == "_" ) {
				throw 0;
			}
			else {
				//decide here whether is it modifiesS or modifiesP
				return modifiesS();
			}
		}
		else if ( nextToken == "Uses" ) {
			match("Uses");
			match("(");
			if ( nextToken == "_" ) {
				throw 0;
			}
			else {
				//decide here whether is it usesS or usesP
				return  usesS();
			}
		}
		//else if ( nextToken == "Calls" ) {
		//	;
		//}
		else if ( nextToken == "Parent" ) {
			match("Parent");
			if ( nextToken == "*" ) {
				match("*");
				return parentT();
			}
			else {
				return parent();
			};
		}
		else if ( nextToken == "Follows" ) {
			match("Follows");
			if ( nextToken == "*" ) {
				match("*");
				return followsT();
			}
			else {
				return follows();
			}
		}
		//else if ( nextToken == "Next" ) {
		//	;
		//}
		//else if ( nextToken == "Affects" ) {
		//	;
		//}
		else {
			throw 0;
		}
	}

	//modifiesS: stmtRef "," varRef ")"
	TNode QueryPreprocessor::modifiesS() {
		TNode modifiesSNode(MODIFIESS, -1);
		QueryTree::setParentChild(&modifiesSNode, stmtRef());
		match(",");
		QueryTree::setParentChild(&modifiesSNode, varRef());
		match(")");
		return modifiesSNode;
	}

	void QueryPreprocessor::modifiesP() {
		;//TO BE IMPLEMENTED
	}

	//usesS: stmtRef "," varRef ")"
	TNode QueryPreprocessor::usesS() {
		TNode usesSNode(USESS, -1);
		QueryTree::setParentChild(&usesSNode, stmtRef());
		match(",");
		QueryTree::setParentChild(&usesSNode, varRef());
		match(")");
		return usesSNode;
	}

	void QueryPreprocessor::usesP() {
		;//TO BE IMPLEMENTED
	}

	void QueryPreprocessor::calls() {
		;//TO BE IMPLEMENTED
	}

	void QueryPreprocessor::callsT() {
		;//TO BE IMPLEMENTED
	}

	TNode QueryPreprocessor::parent() {
		//return TNode with parent relation
		TNode parentNode(PARENT, -1);
		//get the 2 kids and link parentNode with its 2 kids
		match("(");
		QueryTree::setParentChild(&parentNode, stmtRef());
		match(",");
		QueryTree::setParentChild(&parentNode, stmtRef());
		match(")");
		return parentNode;
	}

	TNode QueryPreprocessor::parentT() {
		//return TNode with parentT relation
		TNode parentTNode(PARENTT, -1);
		//get the 2 kids and link parentTNode with its 2 kids
		match("(");
		QueryTree::setParentChild(&parentTNode, stmtRef());
		match(",");
		QueryTree::setParentChild(&parentTNode, stmtRef());
		match(")");
		return parentTNode;
	}

	//follows: "(" stmtRef "," stmtRef ")"
	TNode QueryPreprocessor::follows() {
		//return TNode with follows relation
		TNode followsNode(FOLLOWS, -1);
		//get the 2 kids and link followsNode with its 2 kids
		match("(");
		QueryTree::setParentChild(&followsNode, stmtRef());
		match(",");
		QueryTree::setParentChild(&followsNode, stmtRef());
		match(")");
		return followsNode;
	}

	TNode QueryPreprocessor::followsT() {
		TNode followsTNode(FOLLOWST, -1);
		//get the 2 kids and link followsTNode with its 2 kids
		match("(");
		QueryTree::setParentChild(&followsTNode, stmtRef());
		match(",");
		QueryTree::setParentChild(&followsTNode, stmtRef());
		match(")");
		return followsTNode;
	}

	void QueryPreprocessor::next() {
		;//TO BE IMPLEMENTED
	}

	void QueryPreprocessor::nextT() {
		;//TO BE IMPLEMENTED
	}

	void QueryPreprocessor::affects() {
		;//TO BE IMPLEMENTED
	}

	void QueryPreprocessor::affectsT() {
		;//TO BE IMPLEMENTED
	}

	//pattern: "pattern" patt ( "and" patt )*
	TNode QueryPreprocessor::pattern() {
		match("pattern");
		TNode basePatternNode;
		
		basePatternNode = patt();
		//and...

		return basePatternNode;
	}

	//patt: assign | while | if
	TNode QueryPreprocessor::patt() {
		if ( isSynonym(nextToken) ) {
			//check the type of the synonym
			TYPE synType = DefinitionTable::getType(nextToken);
			if ( synType == ASSIGN ) {
				nextToken = getToken();
				return pAssign();
			}
			else if ( synType == WHILE ) {
				nextToken = getToken();
				return pWhile();
			}
			//else if ( synType == IF ) {
			//	;
			//}
			else {
				throw 0;
			}
		}
		else {
			throw 0;
		}
	}

	//assign: "(" varRef "," ( "_" | expr_spec ) ")"
	TNode QueryPreprocessor::pAssign() {
		TNode pAssignNode(ASSIGN, -1);
		match("(");
		QueryTree::setParentChild(&pAssignNode, varRef());
		match(",");
		QueryTree::setParentChild(&pAssignNode, exprSpec());
		match(")");
		return pAssignNode;
	}

	//while: "(" varRef "," "_" ")"
	TNode QueryPreprocessor::pWhile() {
		return TNode(NONE, -1);
	}

	//if: "(" varRef "," "_" ")"
	TNode QueryPreprocessor::pIf() {
		return TNode(NONE, -1);
	}

	//exprSpec: "_" | """ expr """ | "_" """ expr """ "_"
	TNode QueryPreprocessor::exprSpec() {
		TNode exprSpecNode(EXPR, -1);
		if ( nextToken == "_" ) {
			match("_");
			TNode wildCardNode1(WILDCARD, -1);
			QueryTree::setParentChild(&exprSpecNode, wildCardNode1);
			if ( nextToken == "\"" ) {
				match("\"");
				QueryTree::setParentChild(&exprSpecNode, expr());
				match("\"");
				match("_");
				TNode wildCardNode2(WILDCARD, -1);
				QueryTree::setParentChild(&exprSpecNode, wildCardNode2);
			}
		}
		else if ( nextToken == "\"" ) {
			match("\"");
			QueryTree::setParentChild(&exprSpecNode, expr());
			match("\"");
		}
		else {
			throw 0;
		}
		return exprSpecNode;
	}

	TNode QueryPreprocessor::expr() {
		//expr is basically a well-formed expression. start with var/const and ends with var/const.
		//qn: is synonym allowed? NOPE
		//qn: so any var/const will be in the VarConstTable? NOT NECESSARY
		//convert the whole expr into a vector<TNode>
		//while there are still timesNode in the vector<TNode> || vector<TNode> is larger than 1
		//	set nodes on the left and right as child of timesNode
		//while there are plusNode or minusNode in the vector<TNode> || vector<TNode> is larger than 1
		//	set nodes on the left and right as child of plus\minueNode
		//return vector<TNode>.at(1)
		vector<TNode> nodeList;
		while ( nextToken != "\"" ) {
			if ( nextToken == "+" ) {
				TNode nextNode(PLUS, -1);
				nextToken = getToken();
				nodeList.push_back(nextNode);
			}
			else if ( nextToken == "-" ) {
				TNode nextNode(MINUS, -1);
				nextToken = getToken();
				nodeList.push_back(nextNode);
			}
			else if ( nextToken == "*" ) {
				TNode nextNode(TIMES, -1);
				nextToken = getToken();
				nodeList.push_back(nextNode);
			}
			else {
				//need to handle both VAR and CONST
				//if VAR/CONST not in table, it must return false in QE
				//if in VarTable
				//	TNode nextNode(VAR, VarIndex)
				//else if in ConstTable
				//	TNode nextNode(CONST, ConstIndex)
				//else
				//	TNode nextNode(NONE, -1)
				TNode nextNode(NONE, -1);//placeholder
				nextToken = getToken();
			}
		}
		
		try {
			for ( int i = 0; i < nodeList.size(); ++i ) {
				if ( nodeList.size() == 1 ) {
					break;
				}
				if ( nodeList.at(i).getType() == TIMES ) {
					QueryTree::setParentChild(&nodeList.at(i), nodeList.at(i-1));
					nodeList.erase(nodeList.begin()+i-1);
					--i;
					QueryTree::setParentChild(&nodeList.at(i), nodeList.at(i+1));
					nodeList.erase(nodeList.begin()+i+1);
				}
			}
			
			for ( int i = 0; i < nodeList.size(); ++i ) {
				if ( nodeList.size() == 1 ) {
					break;
				}
				if ( nodeList.at(i).getType() == PLUS || nodeList.at(i).getType() == MINUS ) {
					QueryTree::setParentChild(&nodeList.at(i), nodeList.at(i-1));
					nodeList.erase(nodeList.begin()+i-1);
					--i;
					QueryTree::setParentChild(&nodeList.at(i), nodeList.at(i+1));
					nodeList.erase(nodeList.begin()+i+1);
				}
			}
		} catch (...) {
			throw 0;
		}
		
		return nodeList.at(0);
	}

	//with: "with" attrComp ( "and" attrComp )*
	void QueryPreprocessor::with() {
		;//TO BE IMPLEMENTED
	}

	//TESTERS
	vector<string> QueryPreprocessor::tokeniseTest(string testString) {
		return tokenise(testString);
	}